import random
import HFSP_test.PMX_utils as pmx
import numpy as np
from work_assignment_test.work_assignment_utils import WorkAssignment
import threading
from concurrent.futures import ThreadPoolExecutor


class WorkAssignmentGA:
    def __init__(self, crossover_prob=0.8, mutation_prob=0.1, pop_size=10, chrom_size=8, select_ratio=0.01,
                 generation=500):
        self._crossover_prob = crossover_prob
        self._mutation_prob = mutation_prob
        self._pop_size = pop_size
        self._chrom_size = chrom_size
        self._select_ratio = select_ratio
        self._generation = generation
        self._population = []

    # 获取工作安排的最大适应度
    def get_max_fitness(self):
        max_fitness = -1
        for index, value in enumerate(self._population):
            each_fitness = self.cal_fitness(value)
            if each_fitness >= max_fitness:
                max_fitness = each_fitness
            else:
                pass
        return max_fitness

    # 工作安排的初始化
    def init_population(self):
        job_num = len(job_list)
        p = []
        # 获取一个0-job_num的序列,不重复的
        for j in range(0, job_num):
            p.append(j)
        # 复制很多个随机安排
        population = []
        for index in range(0, self._pop_size):
            new_p = p.copy()
            random.shuffle(new_p)
            population.append(new_p)
        population_np = np.asarray(population)
        self._population = population_np
        pass

    # 计算工作安排的适应度,按照最大时间来算
    def cal_fitness(self, work_assign_sequence):
        each_WorkAssignment = WorkAssignment(person_list, job_list, work_cost_matrix, transport_cost_matrix,
                                             work_assign_sequence)
        each_WorkAssignment.workAssign(work_assign_sequence)
        all_person_max_time = 0
        for each_person in each_WorkAssignment._person_entity_list:
            # 获取每个人最后任务的完成时间
            each_personMaxTime = 0
            if len(each_person._job_done_list) != 0:
                each_personMaxTime = each_person._job_done_list[-1]['end_time']
            else:
                each_personMaxTime = 0
            if each_personMaxTime >= all_person_max_time:
                all_person_max_time = each_personMaxTime
        # 用设置的最大时间减去所有人中最大的时间来衡量适应度
        fitness_val = max_hour - all_person_max_time
        # 可以打印甘特图
        # each_WorkAssignment.drawGantt()

        return fitness_val

    # 原始交叉,会生成两个孩子,目前返回第一个
    def cross(self, child, mother):
        child1 = child.copy()
        child2 = mother.copy()
        new_child1, new_child2 = pmx.two_points_cross(child1.tolist(), child2.tolist())
        return np.asarray(new_child1)

    # 原始变异，这里选择的是二进制的变异
    def mutation(self, chromosome):
        new_chromosome = pmx.changeLocationMutation(chromosome)
        return new_chromosome

    # 生成pop_size个新对象，并在生成的时候染色体进行交叉和随机产生变异
    def cross_and_mutation(self):
        new_population_list = []
        # 前一个和后一个交叉变异
        for father in self._population:
            child = father
            # 产生子代时不是必然发生交叉，而是以一定的概率发生交叉
            if np.random.rand() < self._crossover_prob:
                # 随机找一个母亲
                mother = self._population[np.random.randint(self._pop_size)]
                child = self.cross(child, mother)
            else:
                pass
            child = self.mutation(child)
            new_population_list.append(child)
        new_population = np.asarray(new_population_list)
        # 重新填充
        self._population = new_population.copy()
        pass

    # 使用轮盘赌模式的选择
    def select_normal(self):
        # 每个个体适应度
        every_fitness = []
        # 线程池
        pool = ThreadPoolExecutor(max_workers=len(self._population))
        futureList = []
        for i in self._population:
            work_assign_sequence = i
            future = pool.submit(self.cal_fitness, work_assign_sequence)
            futureList.append(future)

        # 返回线程值
        while every_fitness == len(self._population):
            for eachFuture in futureList:
                each_every_fitness_value = eachFuture.result()
                every_fitness.append(each_every_fitness_value)

        # 所有的适应度
        total_fitness = sum(every_fitness)
        # 转化为数组
        every_fitness_np = np.asarray(every_fitness)
        # 轮盘赌计算
        ids = np.random.choice(np.arange(self._pop_size), size=self._pop_size, replace=True,
                               p=every_fitness_np / total_fitness)

        new_population = self._population[ids]
        self._population = new_population.copy()

    # 精英模式的选择
    def select_elite(self):
        # 获取前百分之十的个体
        best_ten_percent = []
        # 临时排序列表
        value_fitness_list = []
        for index, value in enumerate(self._population):
            work_assign_sequence = value
            # 通过调度顺序获取适应度
            each_every_fitness_value = self.cal_fitness(work_assign_sequence)
            value_fitness_list.append(each_every_fitness_value)
        value_fitness_list_np = np.array(value_fitness_list)
        sort_idx = np.argsort(value_fitness_list_np)
        best_ten_percent = self._population[sort_idx][-int(self._pop_size / 10):]

        # 将这前百分之十复制到剩余的族群中
        new_population = []
        for idx in range(0, self._pop_size):
            select_id = random.randint(0, int(self._pop_size / 10) - 1)
            new_chrome = best_ten_percent[select_id]
            new_population.append(new_chrome)

        self._population = new_population.copy()

    # 选择最好的调度
    def select_best(self):
        max_fitness = 0
        best_work_sequence = []
        for i in self._population:
            work_assign_sequence = i
            # 通过调度顺序获取适应度
            each_every_fitness_value = self.cal_fitness(work_assign_sequence)
            if each_every_fitness_value > max_fitness:
                max_fitness = each_every_fitness_value
                best_work_sequence = i.copy()
        return best_work_sequence

    # 选择最差的调度
    def select_worst(self):
        min_fitness = max_hour
        worst_work_sequence = []
        for i in self._population:
            work_assign_sequence = i
            # 通过调度顺序获取适应度
            each_every_fitness_value = self.cal_fitness(work_assign_sequence)
            if each_every_fitness_value < min_fitness:
                min_fitness = each_every_fitness_value
                worst_work_sequence = i.copy()
        return worst_work_sequence

    # 获取该序列的最长时间
    def get_seq_max_time(self, each_work_assignment):
        all_person_max_time = 0
        for each_person in each_work_assignment._person_entity_list:
            each_personMaxTime = 0
            if len(each_person._job_done_list) != 0:
                each_personMaxTime = each_person._job_done_list[-1]['end_time']
            else:
                each_personMaxTime = 0
            if each_personMaxTime >= all_person_max_time:
                all_person_max_time = each_personMaxTime
        return all_person_max_time

    # 开始执行进化过程
    def evolve(self):
        # 先初始化种群
        self.init_population()
        # 选择一个打印最长时间、序列，画图
        first_seq = self._population[random.randint(0, self._pop_size - 1)]
        first_seq = self.select_best()
        print('first_seq', first_seq)
        first_WorkAssignment = WorkAssignment(person_list,
                                              job_list,
                                              work_cost_matrix,
                                              transport_cost_matrix,
                                              first_seq)
        first_WorkAssignment.workAssign(first_seq)
        # 打印最长时间
        first_max_time = self.get_seq_max_time(first_WorkAssignment)
        print('first_time', first_max_time)
        # 画图
        first_WorkAssignment.drawGantt()
        # 迭代进化
        for each_gen in range(0, self._generation):
            if each_gen <= int(self._generation / 2):
                # 选择（轮盘赌模式）
                self.select_normal()
            else:
                # 精英模式
                self.select_elite()
            # 两个染色体交叉、变异，并重新填充population族群
            # self.crossover_and_mutation()
            # PMX的交叉变异
            self.cross_and_mutation()
            # 获取最大的值
            max_fitness = self.get_max_fitness()
            # 打印
            # print('each_gen' + str(each_gen) + ',' + str(max_fitness))
            print('each_gen' + str(each_gen))

        # 输出最后一代最好的那个
        best_seq = self.select_best()
        print('best_seq', best_seq)
        best_WorkAssignment = WorkAssignment(person_list, job_list,
                                             work_cost_matrix,
                                             transport_cost_matrix,
                                             best_seq)
        best_WorkAssignment.workAssign(best_seq)
        # 打印最长时间
        best_max_time = self.get_seq_max_time(best_WorkAssignment)
        print('best_time', best_max_time)
        # 画图
        best_WorkAssignment.drawGantt()

        pass


if __name__ == '__main__':
    max_hour = 100
    # 人员信息
    person_list = [
        {'person_id': 0, 'person_name': '1号员工'},
        {'person_id': 1, 'person_name': '2号员工'},
        {'person_id': 2, 'person_name': '3号员工'},
        {'person_id': 3, 'person_name': '4号员工'},
    ]
    # 任务信息
    job_list = [
        {'job_id': 0, 'job_name': '1号任务'},
        {'job_id': 1, 'job_name': '2号任务'},
        {'job_id': 2, 'job_name': '3号任务'},
        {'job_id': 3, 'job_name': '4号任务'},
        {'job_id': 4, 'job_name': '5号任务'},
        {'job_id': 5, 'job_name': '6号任务'},
        {'job_id': 6, 'job_name': '7号任务'},
        {'job_id': 7, 'job_name': '8号任务'},
        {'job_id': 8, 'job_name': '9号任务'},
        {'job_id': 9, 'job_name': '10号任务'},
        {'job_id': 10, 'job_name': '11号任务'},
        {'job_id': 11, 'job_name': '12号任务'},
        {'job_id': 12, 'job_name': '13号任务'},
        {'job_id': 13, 'job_name': '14号任务'},
        {'job_id': 14, 'job_name': '15号任务'},
        {'job_id': 15, 'job_name': '16号任务'},
        {'job_id': 16, 'job_name': '17号任务'},
        {'job_id': 17, 'job_name': '18号任务'},
        {'job_id': 18, 'job_name': '19号任务'},
        {'job_id': 19, 'job_name': '20号任务'},
    ]
    # 工时信息，用二维数组表示,表示每个人完成任务所需要的时间，行数为人员号，列为任务号，
    # 比如第一行，第四列表示1号员工（person_id为0）完成4号任务（job_id为3）的时间
    work_cost_matrix = [
        [2, 2, 3, 4, 2, 4, 2, 3, 4, 2, 4, 2, 4, 2, 3, 4, 2, 4, 2, 4],
        [6, 4, 2, 2, 2, 4, 4, 2, 2, 2, 4, 4, 2, 4, 4, 2, 2, 2, 4, 4],
        [6, 3, 1, 1, 2, 4, 3, 1, 1, 2, 4, 3, 2, 4, 3, 1, 1, 2, 4, 3],
        [4, 3, 1, 1, 1, 4, 3, 1, 1, 1, 4, 3, 1, 4, 3, 1, 1, 1, 4, 3],
    ]
    # 固定生成13*13的交通开销矩阵，默认第一行为第一个地点到各个地点的时间开销，
    # 最后一行为公司到各个地点的开销，这样保证序号都是对的上
    # 为便于调试，指定种子值使得该矩阵永远是这个样子
    np.random.seed(1234)
    transport_cost_matrix = np.random.randint(1, 5, (20 + 1, 20 + 1))
    np.random.seed(None)

    mga = WorkAssignmentGA(crossover_prob=0.8, mutation_prob=0.1, pop_size=500, chrom_size=9, select_ratio=0.1,
                           generation=200)
    mga.evolve()

    pass
